package it.softphone.cmc.server.config;

import it.softphone.cmc.server.service.ScriptletManagerService;
import it.softphone.cmc.server.service.SystemManagerService;
import it.softphone.cmc.server.service.TenantService;
import it.softphone.cmc.server.service.exception.CMCException;
import it.softphone.cmc.shared.entity.Account;
import it.softphone.cmc.shared.entity.Asset;
import it.softphone.cmc.shared.entity.AssetImpl;
import it.softphone.cmc.shared.entity.ScriptletAsset;
import it.softphone.cmc.shared.entity.Tenant;
import it.softphone.cmc.shared.entity.TenantUser;
import it.softphone.cmc.shared.entity.Version;
import it.softphone.cmc.shared.security.Permissions;
import it.softphone.cmc.shared.security.Roles;
import it.softphone.commons.json.server.JSONUtils;
import it.softphone.commons.service.scripting.ScriptingService;
import it.softphone.commons.service.scripting.ScriptingServiceFactory;
import it.softphone.commons.service.scripting.rhino.NativeToJSONArrayConverter;
import it.softphone.commons.service.scripting.rhino.NativeToJSONObjectConverter;
import it.softphone.commons.service.security.SecurityAccountService;
import it.softphone.commons.service.security.SecurityAccountServiceFactory;
import it.softphone.commons.spring.ApplicationConfig;
import it.softphone.commons.spring.config.BaseWebConfigSupport;
import it.softphone.commons.util.Util;

import java.io.IOException;
import java.net.URL;
import java.security.GeneralSecurityException;
import java.util.Arrays;
import java.util.ServiceLoader;
import java.util.jar.Manifest;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.script.ScriptException;

import org.apache.commons.logging.Log;
import org.mozilla.javascript.NativeArray;
import org.mozilla.javascript.NativeObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.config.java.annotation.Bean;
import org.springframework.config.java.annotation.Configuration;
import org.springframework.config.java.annotation.Lazy;
import org.springframework.config.java.plugin.context.AnnotationDrivenConfig;

import de.kalpatec.pojosr.framework.launch.PojoServiceRegistryFactory;

/**
 * 
 * @author softphone
 *
 */
@Configuration
@AnnotationDrivenConfig
public class CMCServiceConfig extends BaseWebConfigSupport {
	
	@Autowired(required=true)	org.apache.commons.configuration.Configuration	baseConfig;
	@Autowired(required=true)	SystemManagerService							entityManagerService;
	@Autowired(required=true) 	ScriptletManagerService							scriptletService;
	@Autowired(required=true) 	TenantService									tenantService;
	
	public class Features  {
	
		public SystemManagerService getEntityManager() {
			return entityManagerService;
		}

		public Log getLog() {
			return logger;
		}

		public TenantService getTenantManager() {
			return tenantService;
		}

		public ScriptletManagerService getScriptletManager() {
			return scriptletService;
		}
		
	};
	

	/**
	 * 
	 * @return
	 * @throws GeneralSecurityException
	 */
	@Bean(lazy=Lazy.TRUE) public SecurityAccountService accountService() throws GeneralSecurityException {
		
		try {
			SecurityAccountServiceFactory factory = SecurityAccountServiceFactory.getSecurityAccountServiceFactory();
			
			return factory.newSecurityAccountService(null);
			
		} catch (GeneralSecurityException e) {
			logger.error( "error loading  SecurityAccountService", e );
			throw e;
		}
		
		
	}
	
	/**
	 * 
	 * @return
	 */
	@Bean public PojoServiceRegistryFactory registryFactory() {
		
		ServiceLoader<PojoServiceRegistryFactory> service = ServiceLoader.load(PojoServiceRegistryFactory.class);
		
		if( service==null ) {
			final String message = "PojoServiceRegistryFactory not found!";
			logger.error( message);
			throw new IllegalStateException(message);
		}
		
		java.util.Iterator<PojoServiceRegistryFactory> ii =  service.iterator();
		
		if( !ii.hasNext() ) {
			final String message = "PojoServiceRegistry not found!";
			logger.error( message);
			throw new IllegalStateException(message);
		}
		
		PojoServiceRegistryFactory registryFactory = ii.next();

		
		return registryFactory;
	}
	
	/**
	 * 
	 * @return
	 */
	@Bean(lazy=Lazy.FALSE) public ScriptingService scriptingService() {
		ScriptingService service = null;
		try {
			
			service = ScriptingServiceFactory.getScriptingService(baseConfig);
						
			service.put( "Log", logger);
			service.put( "CMC", new Features()); 
			
		} catch (ScriptException e) {
			
			logger.error( "error getting script service", e );
		}
		
		return service;
	}

	/**
	 * 
	 */
	private void initScripts() {

		logger.info( "INIT SCRIPTS");
		
		try {
			Util.forEachManifest(getClass().getClassLoader(), new Util.ManifestPredicate() {
				
				@Override
				public boolean evaluate(Manifest mf, URL url) {

					logger.debug( String.format("evaluate manifest [%s]", url) );
					
					final java.util.jar.Attributes mainAttrs = mf.getMainAttributes();

					if( mainAttrs != null ) { 
					
						final Object scripts = mainAttrs.getValue("Scripts");
						
						logger.debug( String.format("Scripts: [%s]", scripts) );

						if( scripts != null ) {
							
							String [] scriptArray = scripts.toString().split(",");
							
							for( String key : scriptArray ) {

								Asset asset = AssetImpl.fromKey(key);
								
								final String scriptSource = String.format( "%s/%s.js", asset.getGroupId(), asset.getArtifactId());
							
								java.io.InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream(scriptSource);
								
								logger.debug( String.format("load script: [%s]", scriptSource) );

								if( is == null ) {
									logger.warn( String.format("script [%s] not found!", scriptSource) );							
									continue;
								}
								
								ScriptletAsset sa = scriptletService.makeScriptlet();
								
								try {
									sa.setContent( Util.toString(new java.io.InputStreamReader(is) ) );
									sa.setGroupId(asset.getGroupId());
									sa.setArtifactId(asset.getArtifactId());
									sa.setVersion(asset.getVersion());
									sa.setDescription("build-in script");
									
									scriptletService.saveScriptlet(sa);

								} catch (IOException e) {
									// TODO Auto-generated catch block
									logger.warn( String.format("error reading resource [%s]", scriptSource), e);
								}	
														
							}
						}
					}
					
					return true;
				}
			});
		} catch (IOException e) {
			logger.error( "INIT SCRIPTS ERRROR!", e );
		}
		
	}
	
	/**
	 * create predefined tenants
	 * 
	 */
	private void initTenants() {
		
		logger.info( "INIT TENANTS");
		
		Tenant tenant = entityManagerService.makeTenant();
		
		
		try {
			javax.servlet.ServletContext ctx = ApplicationConfig.getServletContext();
		
			java.io.FileInputStream is = new java.io.FileInputStream( ctx.getRealPath("/images/logo.png")); 

			byte[] bytes = Util.toBytes(is, 512);
			tenant.setLogoB(bytes);
			
			tenant.setName( tenantService.getSystemTenant() );

			it.softphone.commons.json.server.JSONObject attrMap = tenant.encodeAttributes();
			
			attrMap.put("airship.appkey", "laJxA6N5T_2HnOwRfuUgzA");
			attrMap.put("airship.masterkey", "OyqLwVHBRniKDR2UYpe-iA");
			
			tenant.decodeAttributes(attrMap);
			
			
			entityManagerService.createTenant( tenant );
			
		} catch (Exception e) {
			
			logger.error( "INIT TENANTS ERRROR!", e );
		}

		logger.info( String.format("tenant [%s] inserted!", tenant) );
		
	}
	
	/**
	 * create predefined accounts
	 * 
	 * @throws CMCException
	 */
	private void initAccounts() throws CMCException {
		
		//
		// ADMINISTRATOR
		// 
		{
			final Account account =  entityManagerService.makeAccount();
	
			account.setId("admin");
			
			java.util.Map<String,String> attrMap = new java.util.HashMap<String,String>(1);
			attrMap.put("description", "first version");
			
			account.decodeAttributes(attrMap);
			
			final TenantUser user = entityManagerService.makeUser();
			user.getRoles().add(Roles.ADMINISTRATOR);
			user.getRoles().add(Roles.B2B);

			user.getPermissions().addAll( Arrays.asList( (String [])Permissions.Script.ALL) );		
			user.getPermissions().addAll( Arrays.asList( (String [])Permissions.Tenant.ALL) );	
			user.getPermissions().addAll( Arrays.asList( (String [])Permissions.Account.ALL) );	

			user.setTenant(tenantService.getSystemTenant());
			user.setName( account.getId() );
			entityManagerService.createAccount(account, "password", true);
			
			try {
				entityManagerService.createUser(user, account, true);
			} catch (CMCException e ) {
				// TODO rollback account
				
				throw e;
			}
			
			
		}
		
		//
		// GUEST
		// 
		{
			final Account account =  entityManagerService.makeAccount();
	
			account.setId("guest");
			java.util.Map<String,String> attrMap = new java.util.HashMap<String,String>(1);
			
			attrMap.put("description", "first version");
			
			account.decodeAttributes(attrMap);

	
			final TenantUser user = entityManagerService.makeUser();
			//guestUser.getPermissions().addAll( Arrays.asList( (String [])Permissions.Script.ALL) );		
			user.setTenant(tenantService.getSystemTenant());
			user.setName( account.getId() );

			user.getRoles().add(Roles.GUEST);
			user.getRoles().add(Roles.B2B);
			
			
			entityManagerService.createAccount(account, "guest", true);
			
			try {
				entityManagerService.createUser(user, account, true);
			} catch (CMCException e ) {
				// TODO rollback account
				
				throw e;
			}
		}
		
	}
	
	/**
	 * 
	 */
	@SuppressWarnings("unchecked")
	@PostConstruct
	public void postConstruct() throws CMCException {

		logger.info( "POST CONSTRUCT " + entityManagerService );

		JSONUtils.registerConverter(NativeArray.class, new NativeToJSONArrayConverter());
		JSONUtils.registerConverter(NativeObject.class, new NativeToJSONObjectConverter());
		
		final String version = baseConfig.getString("version", "0.0.0");
		
		Version lastVersion = entityManagerService.getLastVersion( version );
			
		logger.info( String.format("Last version deployed [%s]", lastVersion));
		
		if( lastVersion == null || ((Comparable<String>)lastVersion).compareTo(version) == -1 ) {

			logger.info( String.format("Initialize application version [%s]", version));

			/////////////////////////////////////////////
			// INIT CODE
			/////////////////////////////////////////////
			initTenants();
					
			initAccounts();
				
			initScripts();
			
		} else if (!lastVersion.getVersion().equals(version)) {
	
					logger.warn(String.format(
							"last version [%s] is different by the new one [%s]  ",
							lastVersion.getVersion(), version));
	
					// TODO
	
					// ALIGMENT CODE
	
		}
		
	}
	
	@PreDestroy
	public void preDestroy() {

		logger.info( "PRE DESTROY" );
		
	}
	
}
